VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "WaveStream"
Attribute VB_Creatable = True
Attribute VB_Exposed = True
Option Explicit
'--------------------------------------------------------------
' Public Variable Declarations
'--------------------------------------------------------------
Public Recording As Boolean             ' Public Recording Status Indicator...
Public RecDeviceFree As Boolean         ' Public Recording Device Status Indicator...
Public Playing As Boolean               ' Public Recording Status Indicator...
Public PlayDeviceFree As Boolean        ' Public Recording Device Status Indicator...

Public waveChunkSize As Long            ' size of wave data buffer
Public waveCodec As Long                ' acm codec compression format
Public TIMESLICE As Single              ' recording interval...

'--------------------------------------------------------------
Private Const MINSTREAM = 1
Private Const MAXSTREAM = 32
Private CurRecPos(MINSTREAM To MAXSTREAM) As Long  ' Current Recording Buffer Position
Private CurPlayPos(MINSTREAM To MAXSTREAM) As Long ' Current Playing Buffer Position

Private Type WaveData                   ' [Wave Stream Segment]
    Data() As Byte                      ' Wave data byte array
End Type

Private Type WaveArray                  ' [Wave Stream]
    Waves(MAXBUFFERS) As WaveData       ' Array of WaveBuffers
End Type

Private Type uArrayWaves                ' [Array of Wave Streams]
    Stream(MINSTREAM To MAXSTREAM) As WaveArray ' Wave Buffer Array...
    QueuePos(MAXSTREAM - MINSTREAM + 1) As Long ' Wave Buffer Queue Position
End Type

Private PlayWaveBuffer As uArrayWaves    ' Array Of WaveBuffer Data Type
'--------------------------------------------------------------

'--------------------------------------------------------------
Public Sub InitACMCodec(fmtType As Long, Time_Slice As Single)
'--------------------------------------------------------------
    Dim waveFmt As WAVEFORMATEX                             ' Wave format type
'--------------------------------------------------------------
    waveCodec = fmtType                                     ' Save compression format to public variable
    TIMESLICE = Time_Slice                                  ' Save recording interval to public variable
    Call InitWaveFormat(waveFmt, waveCodec, TIMESLICE)      ' Get wave format info
    waveChunkSize = waveFmt.nAvgBytesPerSec * TIMESLICE     ' Save wave buffer size to public variable
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Public Function StreamInQueue() As Long
' Return current stream index in queue for playback
'--------------------------------------------------------------
    StreamInQueue = PlayWaveBuffer.QueuePos(MINSTREAM)
'--------------------------------------------------------------
End Function
'--------------------------------------------------------------

'--------------------------------------------------------------
Public Sub RemoveStreamFromQueue(StreamIdx As Integer)
' Removes A Stream From The Wave PlayBack Queue When PlayBack Is Done
'--------------------------------------------------------------
    Dim Idx As Integer                                      ' Queue Array Element Variable
'--------------------------------------------------------------
    For Idx = MINSTREAM To MAXSTREAM                        ' For Each Stream In The Queue
        If (PlayWaveBuffer.QueuePos(Idx) = StreamIdx) Then  ' If Stream Found In Queue...
            PlayWaveBuffer.QueuePos(Idx) = 0                ' Remove Stream From Queue
        ElseIf (Idx > MINSTREAM) Then                       ' If Not The First Item In The Queue...
            If (PlayWaveBuffer.QueuePos(Idx - 1) = 0) Then  ' If Previous Item Was Removed...
                If (PlayWaveBuffer.QueuePos(Idx) = 0) Then Exit For
                PlayWaveBuffer.QueuePos(Idx - 1) = PlayWaveBuffer.QueuePos(Idx) ' Move Stream Up To New Position
                PlayWaveBuffer.QueuePos(Idx) = 0            ' Remove Stream From Old Position
            End If
        End If
    Next                                                    ' Next Stream In Queue
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Public Sub WaitForCallBack(CallBackBit As Long, cbFlag As Long)
' Waits For Asynchronous Function Callback Bit To Be Set.
'--------------------------------------------------------------
    Do Until (((CallBackBit And cbFlag) = cbFlag) Or _
               (CallBackBit = WHDR_PREPARED) Or _
               (CallBackBit = 0))       ' Check For (CallBack Bit Or Null)...
        DoEvents                        ' Post Events...
    Loop
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Public Sub WaitForACMCallBack(CallBackBit As Long, cbFlag As Long)
' Waits For Asynchronous Function Callback Bit To Be Set.
'--------------------------------------------------------------
    Do Until (((CallBackBit And cbFlag) = cbFlag) Or _
               (CallBackBit = 0))       ' Check For (CallBack Bit Or Null)...
        DoEvents                        ' Post Events...
    Loop
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Private Sub InitWaveHDR(WaveHeader As WAVEHDR, waveFmt As WAVEFORMATEX, BuffSize As Long)
' Initialize's An Input Wave Header's DataBuffer And Size Members...
'--------------------------------------------------------------
    Dim rc As Long                                      ' Function Return Code...
'--------------------------------------------------------------
    WaveHeader.hData = GlobalAlloc(GMEM_MOVEABLE Or GMEM_SHARE Or GMEM_ZEROINIT, BuffSize) ' Allocate Global Memory
    WaveHeader.lpData = GlobalLock(WaveHeader.hData)    ' Lock Memory handle

    WaveHeader.dwBufferLength = BuffSize                ' Get Wave Buffer Size
    WaveHeader.dwFlags = 0                              ' Must Be Set To 0 For (waveOutPrepareHeader & waveInPrepareHeader)
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Private Function FreeWaveHDR(WaveHeader As WAVEHDR) As Boolean
'--------------------------------------------------------------
    Dim rc As Long                                      ' Function return code
'--------------------------------------------------------------
    rc = GlobalUnlock(WaveHeader.lpData)                ' Unlock Global Memory
    rc = GlobalFree(WaveHeader.hData)                   ' Free Global Memory
    
    FreeWaveHDR = True                                  ' Set Default Return Code
'--------------------------------------------------------------
End Function
'--------------------------------------------------------------

'--------------------------------------------------------------
Private Sub InitAcmHDR(hAS As Long, acmHdr As ACMSTREAMHEADER, wavHdr As WAVEHDR)
' Initialize's An Input Wave Header's DataBuffer And Size Members...
'--------------------------------------------------------------
    Dim rc As Long                                      ' Function Return Code...
    Dim OutBytes As Long
'--------------------------------------------------------------
    acmHdr.cbStruct = Len(acmHdr)                       ' Size of header in bytes
    acmHdr.dwStatus = 0                                 ' Must be initialized to 0
    acmHdr.dwUser = 0                                   ' clear user def info
    acmHdr.cbSrcLengthUsed = 0                          ' Must be initialized to 0
    acmHdr.cbDstLengthUsed = 0                          ' Must be initialized to 0
    
    acmHdr.pbSrc = wavHdr.lpData                        ' Copy address of unprocessed data
    acmHdr.cbSrcLength = wavHdr.dwBufferLength          ' Copy size of unprocessed data
    
    rc = acmStreamSize(hAS, acmHdr.cbSrcLength, acmHdr.cbDstLength, ACM_STREAMSIZEF_SOURCE)
    Call AudioErrorHandler(rc, "acmStreamSize")
    
    ' Allocate memory for de/compression
    acmHdr.dwDstUser = GlobalAlloc(GMEM_MOVEABLE Or GMEM_SHARE Or GMEM_ZEROINIT, acmHdr.cbDstLength)                                     ' Allocate Global Memory
    acmHdr.cbDst = GlobalLock(acmHdr.dwDstUser)         ' Lock Memory handle
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'--------------------------------------------------------------
Private Sub FreeAcmHdr(acmHdr As ACMSTREAMHEADER)
' Initialize's An Input Wave Header's DataBuffer And Size Members...
'--------------------------------------------------------------
    Dim rc As Long                                      ' Function Return Code...
'--------------------------------------------------------------
    rc = GlobalUnlock(acmHdr.cbDst)                     ' Unlock Global Memory
    rc = GlobalFree(acmHdr.dwDstUser)                   ' Free Global Memory
'--------------------------------------------------------------
End Sub
'--------------------------------------------------------------

'------------------------------------------------------------------
Public Function RecordWave(hWND As Long, ByVal TCPSocket As Variant) As Boolean
' Records Audio Sounds To A String Buffer And Sends Buffer To TCP/IP Socket...
'------------------------------------------------------------------
    Dim rc As Long                                      ' Function Return Code
    Dim hAS As Long                                     ' ACM stream device
    Dim cWavefmt As WAVEFORMATEX                        ' Wave compression format
    Dim acmHdr As ACMSTREAMHEADER                       ' ACM stream header
    Dim acmHdr_x As ACMSTREAMHEADER                     ' <<Double Buffering>> ACM stream header
    Dim hWaveIn As Long                                 ' Handle To An Input Wave Device
    Dim waveFmt As WAVEFORMATEX                         ' Wave compression format
    Dim WaveInHDR As WAVEHDR                            ' Handle To An Input Wave Device Header
    Dim WaveInHDR_x As WAVEHDR                          ' <<Double Buffering>> Handle To An xtra Input Wave Device Header
'------------------------------------------------------------------
    RecDeviceFree = False                               ' Allocate Recording Device
    
    Do While Not PlayDeviceFree                         ' Wait For Play Device To Free
        DoEvents                                        ' Yield Events...
    Loop                                                ' Check Play Device Status
    
    Call InitWaveFormat(waveFmt, WAVE_FORMAT_PCM, TIMESLICE)   ' Set current wave format
    
    ' Open Input Wave Device, Let WAVE_MAPPER Pick The Best Device...
    rc = waveInOpen(hWaveIn, WAVE_MAPPER, waveFmt, 0&, 0&, CALLBACK_NULL)
    If Not AudioErrorHandler(rc, "WaveInOpen") Then Exit Function ' Validate Function Return Code
    
    '<<Double Buffering>> Initialize Wave Header Format Information
    Call InitWaveHDR(WaveInHDR_x, waveFmt, (waveFmt.nAvgBytesPerSec * TIMESLICE))
        
    ' Initialize Wave Header Format Information
    Call InitWaveHDR(WaveInHDR, waveFmt, (waveFmt.nAvgBytesPerSec * TIMESLICE))
    
    ' <<Double Buffering>> Prepare Input Wave Device Header
    rc = waveInPrepareHeader(hWaveIn, WaveInHDR_x, Len(WaveInHDR_x)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInPrepareHeader_x") Then GoTo ErrorRecordWave

    ' Prepare Input Wave Device Header
    rc = waveInPrepareHeader(hWaveIn, WaveInHDR, Len(WaveInHDR)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInPrepareHeader") Then GoTo ErrorRecordWave
    
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForCallBack(WaveInHDR_x.dwFlags, WHDR_PREPARED)
    
    ' Wait For Wave Header CallBack
    Call WaitForCallBack(WaveInHDR.dwFlags, WHDR_PREPARED)
    
    ' <<Double Buffering>> Add Input Wave (xtra)Buffer To Wave Input Device
    rc = waveInAddBuffer(hWaveIn, WaveInHDR_x, Len(WaveInHDR_x)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInAddBuffer_x") Then GoTo ErrorRecordWave

    ' Add Input Wave Buffer To Wave Input Device
    rc = waveInAddBuffer(hWaveIn, WaveInHDR, Len(WaveInHDR)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInAddBuffer") Then GoTo ErrorRecordWave
        
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForCallBack(WaveInHDR_x.dwFlags, WHDR_PREPARED)
    
    ' Wait For Wave Header CallBack
    Call WaitForCallBack(WaveInHDR.dwFlags, WHDR_PREPARED)
    
    Call InitWaveFormat(cWavefmt, waveCodec, TIMESLICE)   ' Set current wave format
    
    ' Open/Configure an acm Stream Handle For Compression
    rc = acmStreamOpen(hAS, 0&, waveFmt, cWavefmt, 0&, 0&, 0&, ACM_STREAMOPENF_NONREALTIME)
    Call AudioErrorHandler(rc, "acmStreamOpen")
    
    ' Initialize Audio Compression Manager Streaming Headers
    Call InitAcmHDR(hAS, acmHdr, WaveInHDR)
    Call InitAcmHDR(hAS, acmHdr_x, WaveInHDR_x)
    
    ' Prepare acm Stream Header
    rc = acmStreamPrepareHeader(hAS, acmHdr, 0&)
    Call AudioErrorHandler(rc, "acmStreamPrepareHeader")
    
    ' Prepare acm Stream Header
    rc = acmStreamPrepareHeader(hAS, acmHdr_x, 0&)
    Call AudioErrorHandler(rc, "acmStreamPrepareHeader_x")
        
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForACMCallBack(acmHdr_x.dwStatus, ACMSTREAMHEADER_STATUSF_PREPARED)
    
    ' Wait For Wave Header CallBack
    Call WaitForACMCallBack(acmHdr.dwStatus, ACMSTREAMHEADER_STATUSF_PREPARED)
    
    ' Start Input Wave Device Recording...
    rc = waveInStart(hWaveIn)                           ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInStart") Then GoTo ErrorRecordWave
    
    Do
        ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
        Call WaitForCallBack(WaveInHDR_x.dwFlags, WHDR_DONE)
    
        ' <<Double Buffering>> Compress acm Stream Wave Buffer
        rc = acmStreamConvert(hAS, acmHdr_x, ACM_STREAMCONVERTF_BLOCKALIGN)
        If Not AudioErrorHandler(rc, "acmStreamConvert_x") Then GoTo ErrorRecordWave

        rc = SendSoundAll(TCPSocket, acmHdr_x)           ' <<Double Buffering>> Send Sound Buffer To TCPSocket
        If Not Recording Then Exit Do                       ' Evaluate Recording Stop Flag
        
        ' <<Double Buffering>> Add Input Wave (xtra)Buffer To Wave Input Device
        rc = waveInAddBuffer(hWaveIn, WaveInHDR_x, Len(WaveInHDR_x)) ' Validate Return Code
        If Not AudioErrorHandler(rc, "waveInAddBuffer_x") Then GoTo ErrorRecordWave
        
        Call WaitForCallBack(WaveInHDR.dwFlags, WHDR_DONE)  ' Wait For Wave Header CallBack
        
        ' Convert/Compress acm Stream Wave Buffer
        rc = acmStreamConvert(hAS, acmHdr, ACM_STREAMCONVERTF_BLOCKALIGN)
        If Not AudioErrorHandler(rc, "acmStreamConvert") Then GoTo ErrorRecordWave
        
        rc = SendSoundAll(TCPSocket, acmHdr)           ' Send Sound Buffer To TCPSocket
        If Not Recording Then Exit Do                       ' Evaluate Recording Stop Flag
    
        ' Add Input Wave Buffer To Wave Input Device
        rc = waveInAddBuffer(hWaveIn, WaveInHDR, Len(WaveInHDR)) ' Validate Return Code
        If Not AudioErrorHandler(rc, "waveInAddBuffer") Then GoTo ErrorRecordWave
    Loop While Recording                                   ' Continue Recording...
    
    ' <<Double Buffering>> UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr_x, 0&)
    Call AudioErrorHandler(rc, "acmStreamUnprepareHeader_x")
    
    ' UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr, 0&)
    Call AudioErrorHandler(rc, "acmStreamUnprepareHeader")
    
    ' Free globally allocated and locked memory variables...
    Call FreeAcmHdr(acmHdr_x)                           ' Free extra wave header memory
    Call FreeAcmHdr(acmHdr)                             ' Free wave header memory
    
    ' Close acm Stream Handle
    rc = acmStreamClose(hAS, 0&)
    Call AudioErrorHandler(rc, "acmStreamClose")
    
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForCallBack(WaveInHDR_x.dwFlags, WHDR_DONE)
    
    ' Wait For Wave Header CallBack
    Call WaitForCallBack(WaveInHDR.dwFlags, WHDR_DONE)
    
    ' Stop Input Wave Device
    rc = waveInStop(hWaveIn)                            ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInStop") Then GoTo ErrorRecordWave
   
   ' UnPrepare Input Wave Device Header
    rc = waveInUnprepareHeader(hWaveIn, WaveInHDR, Len(WaveInHDR)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInUnPrepareHeader") Then GoTo ErrorRecordWave
   
   ' <<Double Buffering>> UnPrepare Input Wave Device (xtra)Header
    rc = waveInUnprepareHeader(hWaveIn, WaveInHDR_x, Len(WaveInHDR_x)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInUnPrepareHeader_x") Then GoTo ErrorRecordWave
    
    ' Close Input Wave Device
    rc = waveInClose(hWaveIn)                           ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveInClose") Then Exit Function
    
    ' Clean Up Memory Data...
    rc = FreeWaveHDR(WaveInHDR)                         ' Free Wave Header Data
    rc = FreeWaveHDR(WaveInHDR_x)                       ' Free Extra Wave Header Data
    
    RecordWave = True                                   ' Return Success
    RecDeviceFree = True                                ' Free Recording Device
    Exit Function                                       ' Exit
'------------------------------------------------------------------
ErrorRecordWave:                                        ' Clean Up Environment(Brute force no error handling)...
'------------------------------------------------------------------
    rc = acmStreamUnprepareHeader(hAS, acmHdr, 0&)      ' Attempt To UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr_x, 0&)    ' Attempt To UnPrepare acm Stream (xtra)Header
    Call FreeAcmHdr(acmHdr)                             ' Free wave header memory
    Call FreeAcmHdr(acmHdr_x)                           ' Free extra wave header memory
    rc = acmStreamClose(hAS, 0&)                        ' Attempt To Close acm Stream Handle
    
    rc = waveInStop(hWaveIn)                            ' Attempt To Stop WaveInput Device
    rc = waveInReset(hWaveIn)                           ' Attempt To Reset WaveInput Device
    rc = waveInUnprepareHeader(hWaveIn, WaveInHDR, Len(WaveInHDR)) ' Attempt To Unprepare WaveInput Header
    rc = waveInUnprepareHeader(hWaveIn, WaveInHDR_x, Len(WaveInHDR_x)) ' Attempt To Unprepare WaveInput (xtra)Header
    rc = waveInClose(hWaveIn)                           ' Attempt To Close Wave Input Device
    rc = FreeWaveHDR(WaveInHDR)                         ' Free Wave Header Data
    rc = FreeWaveHDR(WaveInHDR_x)                       ' Free Extra Wave Header Data
    
    RecDeviceFree = True                                ' Free Recording Device
    Exit Function                                       ' Exit
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Public Function PlayWave(hWND As Long, StreamIdx As Integer) As Boolean
' Play's Back Audio Wave Data From String Buffers...
'------------------------------------------------------------------
    Dim rc As Long                                      ' Function Return Code
    Dim hAS As Long                                     ' ACM stream device
    Dim acmHdr As ACMSTREAMHEADER                       ' ACM stream header
    Dim acmHdr_x As ACMSTREAMHEADER                     ' <<Double Buffering>> ACM stream header
    Dim cWavefmt As WAVEFORMATEX                        ' Wave compression format
    Dim waveFmt As WAVEFORMATEX                         ' Wave format type
    Dim hWaveOut As Long                                ' Handle To A Wave Output Device
    Dim WaveOutHdr As WAVEHDR                           ' Handle To A Wave Output Device Header
    Dim WaveOutHdr_x As WAVEHDR                         ' Handle To A Wave Output Device Header
'------------------------------------------------------------------
    Call InitWaveFormat(waveFmt, waveCodec, TIMESLICE)   ' Set current wave format
    
    ' Open Output Wave Device
    rc = waveOutOpen(hWaveOut, WAVE_MAPPER, waveFmt, 0&, 0&, CALLBACK_NULL)
    If Not AudioErrorHandler(rc, "waveOutOpen") Then Exit Function ' Validate Return Code
    
    PlayDeviceFree = False                              ' Allocate Recording Device
    
    ' Init Extra Wave Header Format Information
    Call InitWaveHDR(WaveOutHdr_x, waveFmt, (waveFmt.nAvgBytesPerSec * TIMESLICE))
        
    ' Init Wave Header Format Information
    Call InitWaveHDR(WaveOutHdr, waveFmt, (waveFmt.nAvgBytesPerSec * TIMESLICE))
        
    ' Prepare Output Wave Device Header
    rc = waveOutPrepareHeader(hWaveOut, WaveOutHdr_x, Len(WaveOutHdr_x)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveOutPrepareHeader") Then GoTo ErrorPlayWave
    
    ' Prepare Output Wave Device Header
    rc = waveOutPrepareHeader(hWaveOut, WaveOutHdr, Len(WaveOutHdr)) ' Validate Return Code
    If Not AudioErrorHandler(rc, "waveOutPrepareHeader") Then GoTo ErrorPlayWave
    
    ' <<<Double Buffer>>> Copy (extra)Wave Data To Buffer
    If Not (LoadPlayBuffer(hWaveOut, WaveOutHdr_x, waveFmt, _
                           PlayWaveBuffer.Stream(StreamIdx).Waves(CurPlayPos(StreamIdx)).Data, _
                           CurPlayPos(StreamIdx))) Then GoTo ErrorPlayWave ' Cleanup And Leave
    
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForCallBack(WaveOutHdr_x.dwFlags, WHDR_PREPARED)
    
    ' Wait For Wave Header CallBack
    Call WaitForCallBack(WaveOutHdr.dwFlags, WHDR_PREPARED)
    
    'Call InitWaveFormat(cWavefmt, waveCodec, TIMESLICE)   ' Set current wave format
    Call InitWaveFormat(cWavefmt, WAVE_FORMAT_PCM, TIMESLICE)   ' Set current wave format
    
    ' Open/Configure an acm Stream Handle For Compression
    rc = acmStreamOpen(hAS, 0&, waveFmt, cWavefmt, 0&, 0&, 0&, ACM_STREAMOPENF_NONREALTIME)
    Call AudioErrorHandler(rc, "acmStreamOpen")
    
    ' Initialize Audio Compression wave streaming headers...
    Call InitAcmHDR(hAS, acmHdr, WaveOutHdr)
    Call InitAcmHDR(hAS, acmHdr_x, WaveOutHdr_x)
    
    ' Prepare acm Stream Header
    rc = acmStreamPrepareHeader(hAS, acmHdr, 0&)
    Call AudioErrorHandler(rc, "acmStreamPrepareHeader")
    
    ' Prepare acm Stream Header
    rc = acmStreamPrepareHeader(hAS, acmHdr_x, 0&)
    Call AudioErrorHandler(rc, "acmStreamPrepareHeader_x")
        
    ' <<Double Buffering>> Wait For Wave (xtra)Header CallBack
    Call WaitForACMCallBack(acmHdr_x.dwStatus, ACMSTREAMHEADER_STATUSF_PREPARED)
    
    ' Wait For Wave Header CallBack
    Call WaitForACMCallBack(acmHdr.dwStatus, ACMSTREAMHEADER_STATUSF_PREPARED)
    
    ' <<<Double Buffer>>> Write (extra)Wave Buffer To Output Device...
    rc = waveOutWrite(hWaveOut, WaveOutHdr_x, Len(WaveOutHdr_x))
    If Not AudioErrorHandler(rc, "waveOutWrite_x") Then GoTo ErrorPlayWave ' Validate Return Code

    Do
        ' Copy Wave Data To Buffer
        If Not (LoadPlayBuffer(hWaveOut, WaveOutHdr, waveFmt, _
                               PlayWaveBuffer.Stream(StreamIdx).Waves(CurPlayPos(StreamIdx)).Data, _
                               CurPlayPos(StreamIdx))) Then GoTo CleanUpPlayWave ' Cleanup And Leave

        ' <<Double Buffering>> Compress acm Stream Wave Buffer
        rc = acmStreamConvert(hAS, acmHdr, ACM_STREAMCONVERTF_BLOCKALIGN)
        If Not AudioErrorHandler(rc, "acmStreamConvert") Then GoTo ErrorPlayWave
        
        ' Write Wave Buffer To Output Device...
        rc = waveOutWrite(hWaveOut, WaveOutHdr, Len(WaveOutHdr))
        If Not AudioErrorHandler(rc, "waveOutWrite") Then GoTo ErrorPlayWave ' Validate Return Code
            
        ' <<<Double Buffer>>> Wait For Wave Header CallBack
        Call WaitForCallBack(WaveOutHdr_x.dwFlags, WHDR_DONE)
        
        ' <<<Double Buffer>>> Copy (extra)Wave Data To Buffer
        If Not (LoadPlayBuffer(hWaveOut, WaveOutHdr_x, waveFmt, _
                               PlayWaveBuffer.Stream(StreamIdx).Waves(CurPlayPos(StreamIdx)).Data, _
                               CurPlayPos(StreamIdx))) Then GoTo CleanUpPlayWave ' Cleanup And Leave

        ' <<Double Buffering>> Compress acm Stream Wave Buffer
        rc = acmStreamConvert(hAS, acmHdr_x, ACM_STREAMCONVERTF_BLOCKALIGN)
        If Not AudioErrorHandler(rc, "acmStreamConvert_x") Then GoTo ErrorPlayWave
        
        ' <<<Double Buffer>>> Write (extra)Wave Buffer To Output Device...
        rc = waveOutWrite(hWaveOut, WaveOutHdr_x, Len(WaveOutHdr_x))
        If Not AudioErrorHandler(rc, "waveOutWrite_x") Then GoTo ErrorPlayWave ' Validate Return Code
        
        ' Wait For Wave Header CallBack
        Call WaitForCallBack(WaveOutHdr.dwFlags, WHDR_DONE)
    Loop While Playing                                    ' Continue Playing...
    
'------------------------------------------------------------------
CleanUpPlayWave:                                          ' Cleanup...
'------------------------------------------------------------------
    ' <<Double Buffering>> UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr_x, 0&)
    Call AudioErrorHandler(rc, "acmStreamUnprepareHeader_x")
    
    ' UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr, 0&)
    Call AudioErrorHandler(rc, "acmStreamUnprepareHeader")
    
    Call FreeAcmHdr(acmHdr)                             ' Free wave header memory
    Call FreeAcmHdr(acmHdr_x)                           ' Free extra wave header memory
    
    ' Close acm Stream Handle
    rc = acmStreamClose(hAS, 0&)
    Call AudioErrorHandler(rc, "acmStreamClose")
    
    ' Wait For Wave Header CallBack
    Call WaitForCallBack(WaveOutHdr.dwFlags, WHDR_DONE)
    
    ' Unprepare Wave Output Buffer
    rc = waveOutUnprepareHeader(hWaveOut, WaveOutHdr, Len(WaveOutHdr))
    
    ' <<Double Buffer>> Wait For Wave Header CallBack
    Call WaitForCallBack(WaveOutHdr_x.dwFlags, WHDR_DONE)
    
    ' <<Double Buffer>> Unprepare Wave Output Buffer
    rc = waveOutUnprepareHeader(hWaveOut, WaveOutHdr_x, Len(WaveOutHdr_x))

    ' Close Output Wave Device
    rc = waveOutClose(hWaveOut)
    If Not AudioErrorHandler(rc, "waveOutClose") Then Exit Function ' Validate Return Code
    
    ' Clean Up Memory Data...
    rc = FreeWaveHDR(WaveOutHdr)                        ' Free Wave Header Data
    rc = FreeWaveHDR(WaveOutHdr_x)                      ' Free Extra Wave Header Data
    
    PlayWave = True                                     ' Return Success
    PlayDeviceFree = True                               ' Free Recording Device
    Exit Function                                       ' Exit
'------------------------------------------------------------------
ErrorPlayWave:                                          ' Handle Errors And Cleanup...
'------------------------------------------------------------------
    rc = acmStreamUnprepareHeader(hAS, acmHdr, 0&)      ' Attempt To UnPrepare acm Stream Header
    rc = acmStreamUnprepareHeader(hAS, acmHdr_x, 0&)    ' Attempt To UnPrepare acm Stream (xtra)Header
    Call FreeAcmHdr(acmHdr)                             ' Free wave header memory
    Call FreeAcmHdr(acmHdr_x)                           ' Free extra wave header memory
    rc = acmStreamClose(hAS, 0&)                        ' Attempt To Close acm Stream Handle
    
    rc = waveOutUnprepareHeader(hWaveOut, WaveOutHdr, Len(WaveOutHdr)) ' Attempt To Unprepare Header
    rc = waveOutUnprepareHeader(hWaveOut, WaveOutHdr_x, Len(WaveOutHdr_x)) ' Attempt To Unprepare Header
    rc = waveOutClose(hWaveOut)                         ' Close Wave Output Device
    rc = FreeWaveHDR(WaveOutHdr)                        ' Free Wave Header Data
    rc = FreeWaveHDR(WaveOutHdr_x)                      ' Free Extra Wave Header Data
    
    PlayDeviceFree = True                               ' Free Recording Device Flag
    Exit Function                                       ' Exit
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Sub IncBufferPointer(NextVal As Long)
' Moves Buffer Pointer Up One Notch In A Continuous Loop...
'------------------------------------------------------------------
    If NextVal < MAXBUFFERS Then        ' If Not At End Of Buffer
        NextVal = NextVal + 1           ' Increment Buffer Pointer
    Else                                ' At End Of Buffer
        NextVal = MINBUFFERS            ' Go To Beginning Of Buffer
    End If
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Sub InitWaveFormat(waveFmt As WAVEFORMATEX, fmtType As Long, Time_Slice As Single)
' Initializes Wave Format Data Type
'------------------------------------------------------------------
    Dim i As Long
'------------------------------------------------------------------
    Select Case fmtType
    Case WAVE_FORMAT_ADPCM
        waveFmt.wFormatTag = WAVE_FORMAT_ADPCM          ' wave format type
        waveFmt.nChannels = 1                           ' number of channels - mono
        waveFmt.wBitsPerSample = 4                      ' bits/sample of TRUESPEECH - not used.
        waveFmt.nSamplesPerSec = c8_0kHz                ' sample rate kHz
        waveFmt.nAvgBytesPerSec = 4055                  ' Bytes/Sec
        waveFmt.nBlockAlign = 256                       ' block size of data
        waveFmt.cbSize = 2                              ' extra bytes used for WaveFormatEx
        waveFmt.xBytes(0) = &HF9                        ' Fact Chunk - Byte 0
        waveFmt.xBytes(1) = &H1                         ' Fact Chunk - Byte 1
    Case WAVE_FORMAT_MSN_AUDIO          ' Initialize Wave Format - WAVE_FORMAT_MSN_AUDIO
        waveFmt.wFormatTag = WAVE_FORMAT_MSN_AUDIO      ' wave format type
        waveFmt.nChannels = 1                           ' number of channels - mono
        waveFmt.wBitsPerSample = 0                      ' bits/sample of TRUESPEECH - not used.
        waveFmt.cbSize = 4                              ' extra bytes used for WaveFormatEx
        waveFmt.xBytes(0) = &H40                        ' Fact Chunk - Byte 0
        waveFmt.xBytes(1) = &H1                         ' Fact Chunk - Byte 1
'<<< 8.0 kHz - 8200 Bauds >>>  (Fair, No FeedBack)
        waveFmt.nSamplesPerSec = c8_0kHz                ' sample rate kHz
        waveFmt.nAvgBytesPerSec = 1025                  ' Bytes/Sec
        waveFmt.nBlockAlign = 41                        ' block size of data
        waveFmt.xBytes(2) = &H8                         ' Fact Chunk - Byte 2
        waveFmt.xBytes(3) = &H20                        ' Fact Chunk - Byte 3
'<<< 8.0 kHz - 10000 Bauds >>> (Excellent, No FeedBack)
'        WaveFmt.nSamplesPerSec = c8_0kHz                ' sample rate kHz
'        WaveFmt.nAvgBytesPerSec = 1250                  ' Bytes/Sec
'        WaveFmt.nBlockAlign = 50                        ' block size of data
'        WaveFmt.xBytes(2) = &H10                        ' Fact Chunk - Byte 2
'        WaveFmt.xBytes(3) = &H27                        ' Fact Chunk - Byte 3
'<<< 11.025 kHz - 11301 Bauds >>> (Bad, FeedBack)
'<<< 11.025 kHz - 12128 Bauds >>> (Bad, FeedBack)
'<<< 11.025 kHz - 13782 Bauds >>> (Bad, FeedBack)
    Case WAVE_FORMAT_GSM610             ' Initialize Wave Format - WAVE_FORMAT_GSM610
        waveFmt.wFormatTag = WAVE_FORMAT_GSM610         ' wave format type
        waveFmt.nChannels = 1                           ' number of channels - mono
        waveFmt.nSamplesPerSec = c8_0kHz                ' sample rate kHz
        waveFmt.nAvgBytesPerSec = 1625                  ' Bytes/Sec
        waveFmt.nBlockAlign = 65                        ' block size of data
        waveFmt.wBitsPerSample = 0                      ' bits/sample of TRUESPEECH - not used.
        waveFmt.cbSize = 2                              ' extra bytes used for WaveFormatEx
        waveFmt.xBytes(0) = &H40                        ' Fact Chunk - Byte 0
        waveFmt.xBytes(1) = &H1                         ' Fact Chunk - Byte 1
    Case WAVE_FORMAT_PCM                ' Initialize Wave Format - WAVE_FORMAT_PCM
        waveFmt.wFormatTag = WAVE_FORMAT_PCM                ' format type
        waveFmt.nChannels = WAVE_FORMAT_1M08                ' number of channels (i.e. mono, stereo, etc.)
        waveFmt.nSamplesPerSec = c8_0kHz                    ' sample rate 8.0 kHz
        waveFmt.nAvgBytesPerSec = waveFmt.nSamplesPerSec    ' for buffer estimation
        waveFmt.wBitsPerSample = 8                          ' [8, 16, or 0]
        waveFmt.nBlockAlign = waveFmt.nChannels * waveFmt.wBitsPerSample / 8 '  block size of data
        waveFmt.cbSize = 0                                  ' Not Used If [wFormatTag= WAVE_FORMAT_PCM]
    End Select
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

'--------------------------------------------------------------
Public Function AddStreamToQueue(StreamIdx As Integer)
' Puts An Incoming Wave Segment Into The Wave PlayBack Queue
'--------------------------------------------------------------
    Dim Idx As Integer                                  ' Queue Array Processing Variable
'--------------------------------------------------------------
    For Idx = MINSTREAM To MAXSTREAM                    ' For Each Stream In The Queue
        If (PlayWaveBuffer.QueuePos(Idx) = StreamIdx) Then ' If Stream Already In Playback Queue
            AddStreamToQueue = True                     ' Return Success
            Exit Function                               ' Exit
        ElseIf (PlayWaveBuffer.QueuePos(Idx) = 0) Then  ' If Queue Space Available...
            PlayWaveBuffer.QueuePos(Idx) = StreamIdx    ' Put Stream Into The Playback Queue
            AddStreamToQueue = True                     ' Return Success
            Exit Function                               ' Exit
        End If
    Next                                                ' Next Stream In The Queue
'--------------------------------------------------------------
End Function
'--------------------------------------------------------------

'------------------------------------------------------------------
Public Sub SaveStreamBuffer(StreamIdx As Integer, recBuffer() As Byte)
' Saves A Record Buffer To A Record Buffer Array
'------------------------------------------------------------------
    ' If Buffer Is Free
    If (LenB(MidB(PlayWaveBuffer.Stream(StreamIdx).Waves(CurRecPos(StreamIdx)).Data, 1)) < 3) Then
        PlayWaveBuffer.Stream(StreamIdx).Waves(CurRecPos(StreamIdx)).Data = recBuffer ' Copy Buffer From Rec
        Call IncBufferPointer(CurRecPos(StreamIdx)) ' Increment Buffer Pointer To Next Free Position...
    End If                                          ' Else Ignore Buffer Data
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Function LoadPlayBuffer(hWaveOut As Long, WaveOutHdr As WAVEHDR, waveFmt As WAVEFORMATEX, Data() As Byte, playBuffPos As Long) As Boolean
' Loads Audio Sound From A String Buffer Into A Wave Header Structure For PlayBack
'------------------------------------------------------------------
    Dim rc As Long                                  ' Return Code Variable
'------------------------------------------------------------------
    If (LenB(MidB(Data, 1)) > 2) Then
        WaveOutHdr.dwBufferLength = UBound(Data) - LBound(Data) + 1 ' Get Wave Buffer Size
        Call CopyBYTEStoPTR(WaveOutHdr.lpData, Data(0), _
                            WaveOutHdr.dwBufferLength)  ' Copy Buffer From String To Pointer
        Data = ""                                       ' Clear Buffer Space
        Call IncBufferPointer(playBuffPos)              ' Increment Play Buffer ptr To Next Position...
        
        LoadPlayBuffer = True                           ' Return Success
    End If
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Function SendSoundAll(Sockets As Variant, WaveHeader As ACMSTREAMHEADER) As Long
' Sends Sound Buffers To Each Valid Connection In A Connection Array
'------------------------------------------------------------------
    Dim Idx As Integer                              ' Socket cntl index
    Dim rc As Long                                  ' Function Return Code
    Dim Socket As Variant                           ' TCP socket control
'------------------------------------------------------------------
    For Each Socket In Sockets                      ' Check each socket
        If (Socket.State = sckConnected) Then       ' If Connection Is Active
            rc = SendSound(Socket, WaveHeader)      ' Send Sound To Socket Connection
        End If
    Next                                            ' Try Next LocalPort
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Function SendSound(Socket As Variant, acmHdr As ACMSTREAMHEADER) As Long
' Checks A Socket SendFlag Status, And Sends A WaveBuffer When Socket Is Ready
'------------------------------------------------------------------
    Dim WaveBuffer() As Byte                        ' Wave Buffer byte array
'------------------------------------------------------------------
    ReDim WaveBuffer(acmHdr.cbDstLengthUsed - 1) As Byte ' Allocate byte array
    Call CopyPTRtoBYTES(WaveBuffer(0), acmHdr.cbDst, _
                        acmHdr.cbDstLengthUsed)     ' Copy Data
    Call Socket.SendData(WaveBuffer)                ' Send wave data into the socket
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Public Function AudioErrorHandler(rc As Long, fcnName As String) As Boolean
'------------------------------------------------------------------
    Dim msg As String               ' Error Message Body
'------------------------------------------------------------------
    AudioErrorHandler = False       ' Return Failure
    
    Select Case rc Or Err.LastDllError
'   Select Case rc
    Case MMSYSERR_NOERROR           ' no error
        AudioErrorHandler = True    ' Return Success
        Exit Function               ' Exit Function
    Case MMSYSERR_ERROR             ' unspecified error
        msg = "Unspecified Error."
    Case MMSYSERR_BADDEVICEID       ' device ID out of range
        msg = "device ID out of range"
    Case MMSYSERR_NOTENABLED        ' driver failed enable
        msg = "driver failed enable"
    Case MMSYSERR_ALLOCATED         ' device already allocated
        msg = "device already allocated"
    Case MMSYSERR_INVALHANDLE       ' device handle is invalid
        msg = "device handle is invalid"
    Case MMSYSERR_NODRIVER          ' no device driver present
        msg = "no device driver present"
    Case MMSYSERR_NOMEM             ' memory allocation error
        msg = "memory allocation error"
    Case MMSYSERR_NOTSUPPORTED      ' function isn't supported
        msg = "function isn't supported"
    Case MMSYSERR_BADERRNUM         ' error value out of range
        msg = "error value out of range"
    Case MMSYSERR_INVALFLAG         ' invalid flag passed
        msg = "invalid flag passed"
    Case MMSYSERR_INVALPARAM        ' invalid parameter passed
        msg = "invalid parameter passed"
    Case MMSYSERR_LASTERROR         ' last error in range
        msg = "last error in range"
    Case WAVERR_BADFORMAT           ' unsupported wave format
        msg = "unsupported wave format"
    Case WAVERR_STILLPLAYING        ' still something playing
        msg = "still something playing"
    Case WAVERR_UNPREPARED          ' header not prepared
        msg = "header not prepared"
    Case WAVERR_LASTERROR           ' last error in range
        msg = "last error in range"
    Case WAVERR_SYNC                ' device is synchronous
        msg = "device is synchronous"
    Case ACMERR_NOTPOSSIBLE         ' The requested operation cannot be performed
        msg = "The requested operation cannot be performed"
    Case ACMERR_BUSY                ' The stream header specified is currently in use and cannot be unprepared
        msg = "The acm stream header busy"
    Case ACMERR_UNPREPARED
        msg = "The acm stream header is not prepared"
    Case ACMERR_CANCELED
        msg = "The acm operation has been canceled"
    Case ERROR_SHARING_VIOLATION    ' The process cannot access the file because it is being used by another process.
        msg = "The process cannot access the file because it is being used by another process."
    Case Else                       ' Unknown MM Error!
        msg = "Unknown MM Error!"
    End Select
    
    ' Format Text Body Of Message
    msg = "Error In " & fcnName & _
          " rc= " & Str$(rc) & _
          " MSG= " & msg & _
          " LastDllError= " & Hex(Err.LastDllError) & _
          " Source= " & Err.Source & vbCrLf
    
    Debug.Print msg                 ' Print Error Message
    MsgBox msg
    Exit Function                   ' Exit
'------------------------------------------------------------------
End Function
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Sub Class_Initialize()
'------------------------------------------------------------------
    Recording = False                                   ' Set Recording Status Off...
    Playing = False                                     ' Set Playing Status Off...
    RecDeviceFree = True                                ' Set Rec Device Free Status Indicator TRUE
    PlayDeviceFree = True                               ' Set Play Device Free Status Indicator TRUE
    Call InitACMCodec(WAVE_FORMAT_PCM, 0.2)             ' Initialise codec default values...
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Sub Class_Terminate()
'------------------------------------------------------------------
    Recording = False                                   ' Set Recording Status Off...
    Playing = False                                     ' Set Playing Status Off...
    RecDeviceFree = False                               ' Set Rec Device Free Status Indicator TRUE
    PlayDeviceFree = False                              ' Set Play Device Free Status Indicator TRUE
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

'------------------------------------------------------------------
Private Sub debugACM(acmHdr As ACMSTREAMHEADER)
'------------------------------------------------------------------
    ' Used for debugging the audio compression streaming
    MsgBox "cbStruct:" & CStr(acmHdr.cbStruct) & vbCrLf & "dwStatus:" & CStr(acmHdr.dwStatus) & vbCrLf & _
           "dwUser:" & CStr(acmHdr.dwUser) & vbCrLf & _
           "pbSrc:" & CStr(acmHdr.pbSrc) & vbCrLf & _
           "cbSrcLength:" & CStr(acmHdr.cbSrcLength) & vbCrLf & _
           "cbSrcLengthUsed:" & CStr(acmHdr.cbSrcLengthUsed) & vbCrLf & _
           "dwSrcUser:" & CStr(acmHdr.dwSrcUser) & vbCrLf & _
           "cbDst:" & CStr(acmHdr.cbDst) & vbCrLf & _
           "cbDstLength:" & CStr(acmHdr.cbDstLength) & vbCrLf & _
           "cbDstLengthUsed:" & CStr(acmHdr.cbDstLengthUsed) & vbCrLf & _
           "dwDstUser:" & CStr(acmHdr.dwDstUser)
'------------------------------------------------------------------
End Sub
'------------------------------------------------------------------

